/*
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
 *
 * See the LICENSE file for terms of use.
 */

#include "Wt/WApplication.h"
#include "Wt/WFontMetrics.h"
#include "Wt/WPainter.h"
#include "Wt/WPainterPath.h"
#include "Wt/WRectF.h"
#include "Wt/WSvgImage.h"
#include "Wt/WStringStream.h"
#include "Wt/WWebWidget.h"
#include "Wt/Http/Response.h"

#include "WebUtils.h"
#include "ServerSideFontMetrics.h"

#include <cmath>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define SVG ""
//#define SVG "svg:"

namespace {
  double adjust360(double d) {
    if (d > 360.0)
      return 360.0;
    else if (d < -360.0)
      return -360.0;
    else 
      return d;
  }

  bool fequal(double d1, double d2) {
    return std::fabs(d1 - d2) < 1E-5;
  }
}

namespace Wt {

int WSvgImage::nextClipId_ = 0;
int WSvgImage::nextGradientId_ = 0;

WSvgImage::WSvgImage(const WLength& width, const WLength& height,
		     bool paintUpdate)
  : width_(width),
    height_(height),
    painter_(nullptr),
    paintUpdate_(paintUpdate),
    newGroup_(true),
    newClipPath_(false),
    busyWithPath_(false),
    currentClipId_(-1),
    currentFillGradientId_(-1),
    currentStrokeGradientId_(-1),
    currentShadowId_(-1),
    nextShadowId_(0),
    fontMetrics_(nullptr)
{ }

WSvgImage::~WSvgImage()
{
  beingDeleted();
  delete fontMetrics_;
}

WFlags<PaintDeviceFeatureFlag> WSvgImage::features() const
{
  if (ServerSideFontMetrics::available())
    return PaintDeviceFeatureFlag::FontMetrics |
      PaintDeviceFeatureFlag::WordWrap;
  else {
    return PaintDeviceFeatureFlag::WordWrap;
      // Actually, only when outputting to inkscape ...
  }
}

void WSvgImage::init()
{ 
  currentBrush_ = painter()->brush();
  currentPen_ = painter()->pen();
  currentFont_ = painter()->font();

  strokeStyle_ = strokeStyle();
  fillStyle_ = fillStyle();
  fontStyle_ = fontStyle();

  //this is not for clipping, but for settings the initial pen stroke
  //in makeNewGroup()
  newClipPath_ = true;
}

void WSvgImage::done()
{
  finishPath();
}

void WSvgImage::drawArc(const WRectF& rect, double startAngle, double spanAngle)
{
  char buf[30];

  if (std::fabs(spanAngle - 360.0) < 0.01 || spanAngle > 360.0) {
    finishPath();
    makeNewGroup();

    shapes_ << "<" SVG "ellipse "
	    << " cx=\""<< Utils::round_js_str(rect.center().x(), 3, buf);
    shapes_ << "\" cy=\"" << Utils::round_js_str(rect.center().y(), 3, buf);
    shapes_ << "\" rx=\"" << Utils::round_js_str(rect.width() / 2, 3, buf);
    shapes_ << "\" ry=\"" << Utils::round_js_str(rect.height() / 2, 3, buf)
	    << "\" />";
  } else {
    WPainterPath path;

    path.arcMoveTo(rect.x(), rect.y(), rect.width(), rect.height(), startAngle);
    path.arcTo(rect.x(), rect.y(), rect.width(), rect.height(), startAngle,
	       spanAngle);

    drawPath(path);
  }
}

void WSvgImage::setChanged(WFlags<PainterChangeFlag> flags)
{
  if (!flags.empty())
    newGroup_ = true;

  if (flags.test(PainterChangeFlag::Clipping))
    newClipPath_ = true;

  changeFlags_ |= flags;
}

void WSvgImage::makeNewGroup()
{
  if (!newGroup_)
    return;

  bool brushChanged = 
    changeFlags_.test(PainterChangeFlag::Brush) &&
    (currentBrush_ != painter()->brush());

  bool penChanged =
    changeFlags_.test(PainterChangeFlag::Hints) || 
    (changeFlags_.test(PainterChangeFlag::Pen) && 
     (currentPen_ != painter()->pen()));
  bool fontChanged = 
    changeFlags_.test(PainterChangeFlag::Font) && 
    (currentFont_ != painter()->font());
  bool shadowChanged = false;

  if (changeFlags_.test(PainterChangeFlag::Shadow)) {
    if (currentShadowId_ == -1)
      shadowChanged = !painter()->shadow().none();
    else
      shadowChanged = currentShadow_ != painter()->shadow();
  }

  if (shadowChanged)
    newClipPath_ = true;

  if (!newClipPath_) {
    if (!brushChanged && !penChanged) {
      WTransform f = painter()->combinedTransform();

      if (busyWithPath_) {
	if (fequal(f.m11(), currentTransform_.m11()) &&
	    fequal(f.m12(), currentTransform_.m12()) &&
	    fequal(f.m21(), currentTransform_.m21()) &&
	    fequal(f.m22(), currentTransform_.m22())) {

	  /*
	   * Invert scale/rotate to compute the delta needed
	   * before applying these transformations to get the
	   * same as the global translation.
	   */
	  double det = f.m11() * f.m22() - f.m12() * f.m21();
	  double a11 = f.m22() / det;
	  double a12 = -f.m12() / det;
	  double a21 = -f.m21() / det;
	  double a22 = f.m11() / det;

	  double fdx = f.dx() * a11 + f.dy() * a21;
	  double fdy = f.dx() * a12 + f.dy() * a22;

	  const WTransform& g = currentTransform_;

	  double gdx = g.dx() * a11 + g.dy() * a21;
	  double gdy = g.dx() * a12 + g.dy() * a22;

	  double dx = fdx - gdx;
	  double dy = fdy - gdy;

	  pathTranslation_.setX(dx);
	  pathTranslation_.setY(dy);

	  changeFlags_ = None;

	  return;
	}
      } else {
	if (!fontChanged && currentTransform_ == f) {
	  newGroup_ = false;

	  changeFlags_ = None;

	  return;
	}
      }
    }
  }

  newGroup_ = false;

  finishPath();

  char buf[30];

  shapes_ << "</" SVG "g>";

  currentTransform_ = painter()->combinedTransform();

  if (newClipPath_) {
    shapes_ << "</" SVG "g>";
    if (painter()->hasClipping()) {
      currentClipId_ = nextClipId_++;
      shapes_ << "<" SVG "defs><" SVG "clipPath id=\"clip"
	      << currentClipId_ << "\">";

      drawPlainPath(shapes_, painter()->clipPath());

      shapes_ << '"';
      busyWithPath_ = false;

      const WTransform& t = painter()->clipPathTransform();
      if (!t.isIdentity()) {
	shapes_ << " transform=\"matrix("
		<<        Utils::round_js_str(t.m11(), 3, buf);
	shapes_ << ' ' << Utils::round_js_str(t.m12(), 3, buf);
	shapes_ << ' ' << Utils::round_js_str(t.m21(), 3, buf);
	shapes_ << ' ' << Utils::round_js_str(t.m22(), 3, buf);
	shapes_ << ' ' << Utils::round_js_str(t.m31(), 3, buf);
	shapes_ << ' ' << Utils::round_js_str(t.m32(), 3, buf)
		<< ")\"";
      }
      shapes_ << "/></" SVG "clipPath></" SVG "defs>";
    }

    newClipPath_ = false;

    if (shadowChanged) {
      if (!painter()->shadow().none()) {
	if (painter()->shadow() != currentShadow_) {
	  currentShadow_ = painter()->shadow();
	  currentShadowId_ = createShadowFilter(shapes_);
	} else
	  currentShadowId_ = nextShadowId_;
      } else
	currentShadowId_ = -1;
    }

    shapes_ << "<" SVG "g";
    if (painter()->hasClipping())
      shapes_ << clipPath();

    if (currentShadowId_ != -1)
      shapes_ << " filter=\"url(#f" << currentShadowId_ << ")\"";

    shapes_ << '>';
  }

  if (penChanged) {
    currentPen_ = painter()->pen();

    if (!currentPen_.gradient().isEmpty()) {
      currentStrokeGradientId_ = nextGradientId_++;
      defineGradient(currentPen_.gradient(), currentStrokeGradientId_);
    }

    strokeStyle_ = strokeStyle();
  }

  if (brushChanged) {
    currentBrush_ = painter()->brush();

    if (!currentBrush_.gradient().isEmpty()) {
      currentFillGradientId_ = nextGradientId_++;
      defineGradient(currentBrush_.gradient(), currentFillGradientId_);
    }

    fillStyle_ = fillStyle();
  }

  if (fontChanged) {
    currentFont_ = painter()->font();
    fontStyle_ = fontStyle();
  }

  shapes_ << "<" SVG "g style=\""
	  << fillStyle_ << strokeStyle_ << fontStyle_ << '"';

  if (!currentTransform_.isIdentity()) {
    shapes_ << " transform=\"matrix("
	    << Utils::round_js_str(currentTransform_.m11(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(currentTransform_.m12(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(currentTransform_.m21(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(currentTransform_.m22(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(currentTransform_.m31(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(currentTransform_.m32(), 3, buf)
	    << ")\"";
  }

  shapes_ << '>';
  
  changeFlags_ = None;
}

int WSvgImage::createShadowFilter(WStringStream& out)
{
  char buf[30];
  int result = ++nextShadowId_;

  out << "<filter id=\"f" << result
      << "\" width=\"150%\" height=\"150%\">"
      << "<feOffset result=\"offOut\" in=\"SourceAlpha\" dx=\""
      << Utils::round_js_str(currentShadow_.offsetX(), 3, buf) << "\" dy=\"";
  out << Utils::round_js_str(currentShadow_.offsetY(), 3, buf) << "\" />";

  out << "<feColorMatrix result=\"colorOut\" in=\"offOut\" "
      << "type=\"matrix\" values=\"";
  double r = currentShadow_.color().red() / 255.;
  double g = currentShadow_.color().green() / 255.;
  double b = currentShadow_.color().blue() / 255.;
  double a = currentShadow_.color().alpha() / 255.;

  out << "0 0 0 " << Utils::round_js_str(r, 3, buf) << " 0 ";
  out << "0 0 0 " << Utils::round_js_str(g, 3, buf) << " 0 ";
  out << "0 0 0 " << Utils::round_js_str(b, 3, buf) << " 0 ";
  out << "0 0 0 " << Utils::round_js_str(a, 3, buf) << " 0\"/>";
  out << "<feGaussianBlur result=\"blurOut\" in=\"colorOut\" stdDeviation=\""
      << Utils::round_js_str(std::sqrt(currentShadow_.blur()), 3, buf) << "\" />"
    "<feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />"
    "</filter>";

  return result;
}

void WSvgImage::defineGradient(const WGradient& gradient, int id)
{
  char buf[30];

  shapes_ << "<defs>";
  bool linear = gradient.style() == GradientStyle::Linear;

  // linear or radial + add proper position parameters
  if (linear) {
    shapes_ << "<linearGradient gradientUnits=\"userSpaceOnUse\" ";
    shapes_ << "x1=\"" << gradient.linearGradientVector().x1() << "\" "
	    << "y1=\"" << gradient.linearGradientVector().y1() << "\" "
	    << "x2=\"" << gradient.linearGradientVector().x2() << "\" "
	    << "y2=\"" << gradient.linearGradientVector().y2() << "\" ";
  } else {
    shapes_ << "<radialGradient gradientUnits=\"userSpaceOnUse\" ";
    shapes_ << "cx=\"" << gradient.radialCenterPoint().x() << "\" "
	    << "cy=\"" << gradient.radialCenterPoint().y() << "\" "
	    << "r=\"" << gradient.radialRadius() << "\" "
	    << "fx=\"" << gradient.radialFocalPoint().x() << "\" "
	    << "fy=\"" << gradient.radialFocalPoint().y() << "\" ";
  }

  shapes_ << "id=\"gradient" << id << "\">";

  // add colorstops
  for (unsigned i = 0; i < gradient.colorstops().size(); i++) {
    shapes_ << "<stop ";
    std::string offset = std::to_string((int)(gradient.colorstops()[i]
					      .position()*100));
    offset += '%';
    shapes_ << "offset=\"" << offset << "\" ";
    shapes_ << "stop-color=\"" <<
      gradient.colorstops()[i].color().cssText()
	    << "\" ";
    shapes_ << "stop-opacity=\"" <<
      Utils::round_css_str(gradient.colorstops()[i].color().alpha() / 255.,
			   3, buf)
	    << "\" ";
    shapes_ << "/>";
  }

  if (linear)
    shapes_ << "</linearGradient>";
  else
    shapes_ << "</radialGradient>";
  shapes_ << "</defs>";
}

void WSvgImage::drawPlainPath(WStringStream& out, const WPainterPath& path)
{
  char buf[30];

  if (!busyWithPath_) {
    out << "<" SVG "path d=\"";
    busyWithPath_ = true;
    pathTranslation_.setX(0);
    pathTranslation_.setY(0);
  }

  const std::vector<WPainterPath::Segment>& segments = path.segments();

  if (!segments.empty()
      && segments[0].type() != MoveTo)
    out << "M0,0";

  for (unsigned i = 0; i < segments.size(); ++i) {
    const WPainterPath::Segment s = segments[i];

    if (s.type() == ArcC) {
      WPointF current = path.positionAtSegment(i);

      const double cx = segments[i].x();
      const double cy = segments[i].y();
      const double rx = segments[i+1].x();
      const double ry = segments[i+1].y();
      const double theta1 = -WTransform::degreesToRadians(segments[i+2].x());
      const double deltaTheta
	= -WTransform::degreesToRadians(adjust360(segments[i+2].y()));

      i += 2;

      /*
       * formulas from:
       * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
       * with phi = 0
       */
      const double x1 = rx * std::cos(theta1) + cx;
      const double y1 = ry * std::sin(theta1) + cy;
      const double x2 = rx * std::cos(theta1 + deltaTheta / 2.0) + cx;
      const double y2 = ry * std::sin(theta1 + deltaTheta / 2.0) + cy;
      const double x3 = rx * std::cos(theta1 + deltaTheta) + cx;
      const double y3 = ry * std::sin(theta1 + deltaTheta) + cy;
      // const int fa = (std::fabs(deltaTheta) > M_PI ? 1 : 0);
      const int fa = 0;
      const int fs = (deltaTheta > 0 ? 1 : 0);

      if (!fequal(current.x(), x1) || !fequal(current.y(), y1)) {
	out << 'L' << Utils::round_js_str(x1 + pathTranslation_.x(), 3, buf);
	out << ',' << Utils::round_js_str(y1 + pathTranslation_.y(), 3, buf);
      }

      out << 'A' << Utils::round_js_str(rx, 3, buf);
      out << ',' << Utils::round_js_str(ry, 3, buf);
      out << " 0 " << fa << "," << fs;
      out << ' ' << Utils::round_js_str(x2 + pathTranslation_.x(), 3, buf);
      out << ',' << Utils::round_js_str(y2 + pathTranslation_.y(), 3, buf);

      out << 'A' << Utils::round_js_str(rx, 3, buf);
      out << ',' << Utils::round_js_str(ry, 3, buf);
      out << " 0 " << fa << "," << fs;
      out << ' ' << Utils::round_js_str(x3 + pathTranslation_.x(), 3, buf);
      out << ',' << Utils::round_js_str(y3 + pathTranslation_.y(), 3, buf);
    } else {
      switch (s.type()) {
      case MoveTo:
	out << 'M';
	break;
      case LineTo:
	out << 'L';
	break;
      case CubicC1:
	out << 'C';
	break;
      case CubicC2:
      case CubicEnd:
	out << ' ';
	break;
      case QuadC:
	out << 'Q';
	break;
      case QuadEnd:
	out << ' ';
	break;
      default:
	assert(false);
      }

      out << Utils::round_js_str(s.x() + pathTranslation_.x(), 3, buf);
      out << ',' << Utils::round_js_str(s.y() + pathTranslation_.y(), 3, buf);
    }
  }
}

void WSvgImage::finishPath()
{
  if (busyWithPath_) {
    busyWithPath_ = false;

    shapes_ << "\" />";
  }
}

void WSvgImage::drawRect(const WRectF& rectangle)
{
  drawPath(rectangle.toPath());
}

void WSvgImage::drawPath(const WPainterPath& path)
{
  if (path.isEmpty())
    return;

  WRectF bbox = painter()->worldTransform().map(path.controlPointRect());
  if (busyWithPath_) {
    if (pathBoundingBox_.intersects(bbox))
      finishPath();
    else
      pathBoundingBox_ = pathBoundingBox_.united(bbox);
  } else {
    pathBoundingBox_ = bbox;
  }

  makeNewGroup();

  drawPlainPath(shapes_, path);
}

void WSvgImage::drawImage(const WRectF& rect, const std::string& imgUri,
			  int imgWidth, int imgHeight,
			  const WRectF& srect)
{
  finishPath();
  makeNewGroup();

  WApplication *app = WApplication::instance();
  std::string imageUri = imgUri;
  if (app)
    imageUri = app->resolveRelativeUrl(imgUri);

  WRectF drect = rect;

  char buf[30];

  bool transformed = false;

  if (drect.width() != srect.width()
      || drect.height() != srect.height()) {
    shapes_ << "<" SVG "g transform=\"matrix("
	    << Utils::round_js_str(drect.width() / srect.width(), 3, buf);
    shapes_ << " 0 0 " 
	    << Utils::round_js_str(drect.height() / srect.height(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(drect.x(), 3, buf);
    shapes_ << ' ' << Utils::round_js_str(drect.y(), 3, buf) << ")\">";

    drect = WRectF(0, 0, srect.width(), srect.height());

    transformed = true;
  }

  double scaleX = drect.width() / srect.width();
  double scaleY = drect.height() / srect.height();

  double x = drect.x() - srect.x() * scaleX;
  double y = drect.y() - srect.y() * scaleY;
  //double width = drect.width() * imgWidth / srect.width();
  //double height = drect.height() * imgHeight / srect.height();
  double width = imgWidth;
  double height = imgHeight;

  bool useClipPath = false;

  int imgClipId = nextClipId_++;

  if (WRectF(x, y, width, height) != drect) {
    shapes_ << "<" SVG "clipPath id=\"imgClip" << imgClipId << "\">";
    shapes_ << "<" SVG "rect x=\"" << Utils::round_js_str(drect.x(), 3, buf) << '"';
    shapes_ << " y=\"" << Utils::round_js_str(drect.y(), 3, buf) << '"';
    shapes_ << " width=\"" << Utils::round_js_str(drect.width(), 3, buf) << '"';
    shapes_ << " height=\"" << Utils::round_js_str(drect.height(), 3, buf) << '"';
    shapes_ << " /></" SVG "clipPath>";
    useClipPath = true;
  }

  shapes_ << "<" SVG "image xlink:href=\"" << imageUri << "\"";
  shapes_ << " x=\"" << Utils::round_js_str(x, 3, buf) << '"';
  shapes_ << " y=\"" << Utils::round_js_str(y, 3, buf) << '"';
  shapes_ << " width=\"" << Utils::round_js_str(width, 3, buf) << '"';
  shapes_ << " height=\"" << Utils::round_js_str(height, 3, buf) << '"';

  if (useClipPath)
    shapes_ << " clip-path=\"url(#imgClip" << imgClipId << ")\"";

  shapes_ << "/>";

  if (transformed)
    shapes_ << "</" SVG "g>";
}

void WSvgImage::drawLine(double x1, double y1, double x2, double y2)
{
  WPainterPath path;
  path.moveTo(x1, y1);
  path.lineTo(x2, y2);
  drawPath(path);
}

void WSvgImage::drawText(const WRectF& rect, 
			 WFlags<AlignmentFlag> flags,
			 TextFlag textFlag,
			 const WString& text,
			 const WPointF *clipPoint)
{
  if (clipPoint && painter() && !painter()->clipPath().isEmpty()) {
    if (!painter()->clipPathTransform().map(painter()->clipPath())
	  .isPointInPath(painter()->worldTransform().map(*clipPoint)))
      return;
  }

  finishPath();
  makeNewGroup();

  char buf[30];
  WStringStream style;

  // SVG uses fill color to fill text, but we want pen color.
  style << "style=\"stroke:none;";
  if (painter()->pen().color() != painter()->brush().color()
      || painter()->brush().style() == BrushStyle::None) {
    const WColor& color = painter()->pen().color();
    style << "fill:" + color.cssText() << ';'
	  << "fill-opacity:" 
	  << Utils::round_css_str(color.alpha() / 255., 3, buf)
	  << ';';
  }
  style << '"';

  AlignmentFlag horizontalAlign = flags & AlignHorizontalMask;
  AlignmentFlag verticalAlign = flags & AlignVerticalMask;

  if (textFlag == TextFlag::WordWrap) {
    std::string hAlign;
    switch (horizontalAlign) {
    case AlignmentFlag::Left:
      hAlign = "start";
      break;
    case AlignmentFlag::Right:
      hAlign = "end";
      break;
    case AlignmentFlag::Center:
      hAlign = "center";
      break;
    case AlignmentFlag::Justify:
      hAlign = "justify";
    default:
      break;
    }

    shapes_ << "<" SVG "flowRoot " << style.str() << ">\n"
	    << "  <" SVG "flowRegion>\n"
	    << "    <" SVG "rect"
	    <<            " width=\"" << rect.width() << "\""
	    <<            " height=\"" << rect.height() << "\""
	    <<            " x=\"" << rect.x() << "\""
	    <<            " y=\"" << rect.y() << "\""
	    << "    />\n"
	    << "  </" SVG "flowRegion>\n"
	    << "  <" SVG "flowPara"
	    <<              " text-align=\"" << hAlign << "\">\n"
	    << " " << WWebWidget::escapeText(text, false).toUTF8() << "\n"
	    << "  </" SVG "flowPara>\n"
	    << "</" SVG "flowRoot>\n";
  } else {
    shapes_ << "<" SVG "text " << style.str();

    switch (horizontalAlign) {
    case AlignmentFlag::Left:
      shapes_ << " x=" << quote(rect.left());
      break;
    case AlignmentFlag::Right:
      shapes_ << " x=" << quote(rect.right())
	      << " text-anchor=\"end\"";
      break;
    case AlignmentFlag::Center:
      shapes_ << " x=" << quote(rect.center().x())
	      << " text-anchor=\"middle\"";
      break;
    default:
      break;
    }

    /*
     * Opera doesn't do dominant-baseline yet
     */
#if 0
    switch (verticalAlign) {
    case AlignmentFlag::AlignSide::Side::Top:
      shapes_ << " y=" << quote(rect.top())
	      << " dominant-baseline=\"text-before-edge\"";
      break;
    case AlignmentFlag::AlignBottom:
      shapes_ << " y=" << quote(rect.bottom())
	      << " dominant-baseline=\"text-after-edge\"";
      break;
    case AlignmentFlag::Middle:
      shapes_ << " y=" << quote(rect.center().y())
	      << " dominant-baseline=\"middle\"";
      break;
    default:
      break;
    }

    shapes << ">" << WWebWidget::escapeText(text, false).toUTF8() 
	   << "</" SVG "text>";

#else

    /*
     * Workaround: estimate the location of the default baseline which
     * corresponds with the font baseline.
     */
    double fontSize = painter()->font().sizeLength(16).toPixels();

    double y = rect.center().y();
    switch (verticalAlign) {
    case AlignmentFlag::Top:
      y = rect.top() + fontSize * 0.75; break;
    case AlignmentFlag::Middle:
      y = rect.center().y() + fontSize * 0.25; break;
    case AlignmentFlag::Bottom:
      y = rect.bottom() - fontSize * 0.25 ; break;
    default:
      break;
    }

  shapes_ << " y=" << quote(y);

  shapes_ << ">" << WWebWidget::escapeText(text, false).toUTF8() 
	  << "</" SVG "text>";
#endif
  }
}

WTextItem WSvgImage::measureText(const WString& text, double maxWidth,
				 bool wordWrap)
{
  if (!fontMetrics_)
    fontMetrics_ = new ServerSideFontMetrics();

  return fontMetrics_->measureText(painter()->font(), text, maxWidth, wordWrap);
}

WFontMetrics WSvgImage::fontMetrics()
{
  if (!fontMetrics_)
    fontMetrics_ = new ServerSideFontMetrics();

  return fontMetrics_->fontMetrics(painter()->font());
}

std::string WSvgImage::quote(const std::string& s)
{
  return '"' + s + '"';
}

std::string WSvgImage::quote(double d)
{
  char buf[30];
  return quote(Utils::round_js_str(d, 3, buf));
}

std::string WSvgImage::fillStyle() const
{
  char buf[30];
  std::string result;

  switch (painter()->brush().style()) {
  case BrushStyle::None:
    result += "fill:none;";
    break;
  case BrushStyle::Solid:
    {
      const WColor& color = painter()->brush().color();
      result += "fill:" + color.cssText() + ";";
      if (color.alpha() != 255) {
	result += "fill-opacity:";
	result += Utils::round_css_str(color.alpha() / 255., 3, buf);
	result += ';';
      }

      break;
    }
  case BrushStyle::Gradient:
    if (!currentBrush_.gradient().isEmpty()) {
      result += "fill:"; 
      result += "url(#gradient";
      result += std::to_string(currentFillGradientId_);
      result += ");";
    }
  }

  return result;
}

std::string WSvgImage::clipPath() const
{
  if (painter()->hasClipping())
    return " clip-path=\"url(#clip" + std::to_string(currentClipId_) + ")\"";
  else
    return std::string();
}

std::string WSvgImage::strokeStyle() const
{
  WStringStream result;

#ifndef WT_TARGET_JAVA
  char buf[30];
#else
  char *buf;
#endif

  const WPen& pen = painter()->pen();

  if (!(painter()->renderHints() & RenderHint::Antialiasing))
    result << "shape-rendering:optimizeSpeed;";

  if (pen.style() != PenStyle::None) {
    const WColor& color = pen.color();

    if (!pen.gradient().isEmpty()) {
      result << "stroke:url(#gradient"
	     << std::to_string(currentStrokeGradientId_)
	     << ");";
    } else {
      result << "stroke:" << color.cssText() << ';';
      if (color.alpha() != 255)
	result << "stroke-opacity:"
	       << Utils::round_css_str(color.alpha() / 255., 2, buf) << ';';
    }

    WLength w = painter()->normalizedPenWidth(pen.width(), true);
    if (w != WLength(1))
      result << "stroke-width:" << w.cssText() << ";";

    switch (pen.capStyle()) {
    case PenCapStyle::Flat:
      break;
    case PenCapStyle::Square:
      result << "stroke-linecap:square;";
      break;
    case PenCapStyle::Round:
      result << "stroke-linecap:round;";
    }

    switch (pen.joinStyle()) {
    case PenJoinStyle::Miter:
      break;
    case PenJoinStyle::Bevel:
      result << "stroke-linejoin:bevel;";
      break;
    case PenJoinStyle::Round:
      result << "stroke-linejoin:round;";
    }

    switch (pen.style()) {
    case PenStyle::None:
      break;
    case PenStyle::SolidLine:
      break;
    case PenStyle::DashLine:
      result << "stroke-dasharray:4,2;";
      break;
    case PenStyle::DotLine:
      result << "stroke-dasharray:1,2;";
      break;
    case PenStyle::DashDotLine:
      result << "stroke-dasharray:4,2,1,2;";
      break;
    case PenStyle::DashDotDotLine:
      result << "stroke-dasharray:4,2,1,2,1,2;";
      break;
    }
  }

  return result.c_str();
}

std::string WSvgImage::fontStyle() const
{
  return painter()->font().cssText(false);
}

std::string WSvgImage::rendered()
{
  std::stringstream s;
  streamResourceData(s);
  return s.str();
}

void WSvgImage::handleRequest(const Http::Request& request,
			      Http::Response& response)
{
  response.setMimeType("image/svg+xml");

#ifndef WT_TARGET_JAVA
  std::ostream& o = response.out();
#else
  std::ostream o(response.out());
#endif // WT_TARGET_JAVA

  streamResourceData(o);
}

void WSvgImage::streamResourceData(std::ostream& stream)
{
  finishPath();

  if (paintUpdate_)
    stream << "<" SVG "g xmlns=\"http://www.w3.org/2000/svg\""
      " xmlns:xlink=\"http://www.w3.org/1999/xlink\"><" SVG "g><" SVG "g>"
	   << shapes_.str()
	   << "</" SVG "g></" SVG "g></" SVG "g>";
  else
    stream << "<" SVG "svg xmlns=\"http://www.w3.org/2000/svg\""
      " xmlns:xlink=\"http://www.w3.org/1999/xlink\""
      " version=\"1.1\" baseProfile=\"full\""
      " width=\"" << width().cssText() << "\""
      " height=\"" << height().cssText() << "\">"
	   << "<" SVG "g><" SVG "g>" << shapes_.str()
	   << "</" SVG "g></" SVG "g></" SVG "svg>";
}

}
